package com.exam.utils;

import java.security.SecureRandom;

/**
 * @author LG
 */
public class SnowflakeManager {

    private static final long EPOCH_STAMP = 1262275200000L;
    private static final long SEQUENCE_BIT = 12L;
    private static final long MACHINE_BIT = 5L;
    private static final long DATA_CENTER_BIT = 5L;
    private static final long MAX_SEQUENCE_NUM = -1L ^ (-1L << SEQUENCE_BIT);
    private static final long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private static final long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
    private static final long MACHINE_LEFT = SEQUENCE_BIT;
    private static final long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private static final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT + DATA_CENTER_BIT;

    public synchronized static long nextValue(long machineId, long dataCenterId){
        long sequence = 0L;
        long lastTimestamp = -1L;
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException(String.format("machine id can't be greater than %d or less than 0", MAX_MACHINE_NUM));
        }
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("data center id can't be greater than %d or less than 0", MAX_DATA_CENTER_NUM));
        }
        try{
            String os = System.getProperty("os.name");
            SecureRandom secureRandom;
            if (os.toLowerCase().startsWith("win")) {
                // windows机器用
                secureRandom = SecureRandom.getInstanceStrong();
            } else {
                // linux机器用
                secureRandom = SecureRandom.getInstance("NativePRNGNonBlocking");
            }
            long currentTimeMillis = currentTimeMillis();
            if(currentTimeMillis < lastTimestamp) {
                throw new Exception(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", (lastTimestamp-currentTimeMillis)));
            }

            if(lastTimestamp == currentTimeMillis) {
                sequence = (sequence+1) & MAX_SEQUENCE_NUM;
                if (sequence == 0) {
                    sequence = secureRandom.nextInt(Long.valueOf(SEQUENCE_BIT).intValue());
                    currentTimeMillis = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = secureRandom.nextInt(Long.valueOf(SEQUENCE_BIT).intValue());
            }
            lastTimestamp = currentTimeMillis;

            long nextId = ((currentTimeMillis-EPOCH_STAMP) << TIMESTAMP_LEFT)
                    | (dataCenterId << DATA_CENTER_LEFT)
                    | (machineId << MACHINE_LEFT)
                    | sequence;

            return nextId;
        }catch (Exception e){
            e.printStackTrace();
        }
        return 0L;
    }

    private static long tilNextMillis(long lastTimestamp) {
        long currentTimeMillis = currentTimeMillis();
        while (currentTimeMillis <= lastTimestamp) {
            currentTimeMillis = currentTimeMillis();
        }
        return currentTimeMillis;
    }

    private static long currentTimeMillis() {
        return System.currentTimeMillis();
    }
}
